from copy import deepcopy
from .data import Data

# ====================================================================
#
# Comparison object
#
# ====================================================================

class Comparison(object):
    '''

Create an object for storing a comparison operation.

The comparison operation is an operator with a right hand side
operand, such as 'less than 3' and 'is a member of set(1, 3, 4)'. This
comparison operation may be evaluated for an arbitrary left hand side
operand, the result being dependent on its type. For example, if the
comparison operation is 'equals 2' then if this is evaluated for a
left hand side of 2 then True is returned and if it is evaluated for a
left hand side of numpy.array([0, 1, 2] then numpy.array([False,
False, True]) is returned.

The comparison operation is evaluated for an arbitrary left hand side
operand with the `evaluate` method or, equivalently, the ``==``
operator. The ``!=`` operator is also available.

The right hand side operand is stored in the `value` attribute and the
operator is stored in the `operator` attribute and may be any one of
the following:

=========  ==========================================================
Operation  Description
=========  ==========================================================
'lt'       Is strictly less than
'le'       Is less than or equal to
'gt'       Is strictly greater than
'ge'       Is greater than or equal to
'eq'       Is equal to
'ne'       Is not equal to
'wi'       Is within a given range (range bounds included)
'wo'       Is without a given range (range bounds excluded)

'set'      Is a member of a collection of one or more objects. If the
           left hand side is an iterable which also supports
           broadcasting then each element is tested for membership
           independently.
=========  ==========================================================


As a convenience, for each operator in the above list there is an
identically named constructor function which returns the appropriate
Comparison object.

**Examples**

>>> c = cf.Comparison('le', 5)
>>> c.evaluate(4)
True
>>> 5 == c
True
>>> 5 != c
False
>>> 6 != c
True

>>> c = lt(5)
>>> c.evaluate(4)
True

>>> c = cf.Comparison('wi', (1,2))
>>> a = numpy.arange(4)
>>> print c.evaluate(a)
[False True True False]
>>> print a != c
[True False False True]


>>> c = cf.set((1,2))
>>> 2 == c
True
>>> 3 == c
False
>>> print numpy.arange(4) == c
[False True True False]

'''

    def __init__(self, operator, value, units=None):
        '''

**Initialization**

:Parameters:

    operator : str
        The comparison operator.

    value : object
        The value on the right hand side of the comparison operation.

    units : str or Units, optional
        The units of `value`. By default, the same units as the left
        hand side of the comparison operation are assumed.

'''
        if units is not None:
            if not hasattr(value, 'Units'):
                value = Data(value, units=units)
            elif not value.Units.equivalent(units):
                raise ValueError("sdfsdfsd99885109^^^^")
        #--- End: if

        if operator == 'set' and not hasattr(value, '__iter__'):
            value = (value,)

        self.operator = operator
        '''

**Examples**

>>> c.operator = 'eq'
>>> c.operator
'eq'
>>> del c.operator

'''
        self.value = value
        '''

**Examples**

>>> c.value = 'Atlantic'
>>> c.value = 2.0
>>> c.value = cf.Data(2.0)
>>> c.value = cf.Data(2.0, units='m')
>>> c.value = [1, 2, 4]
>>> c.value = (1, 2, 4)
>>> c.value = set([1, 2, 4])
>>> c.value
set([1, 2, 4])
>>> del c.value

'''
    #--- End: def

    def __eq__(self, x):
        '''
x.__eq__(y) <==> x==y <==> x.evaluate(y)

'''
        return self.evaluate(x)
    #--- End: def

    def __ne__(self, x):
        '''
x.__ne__(y) <==> x!=y <==> x.evaluate(y)==False

'''
        return self.evaluate(x) == False
    #--- End: def

    def __repr__(self):
        '''
x.__repr__() <==> repr(x)

'''
        return '<CF %s: %s>' % (self.__class__.__name__, str(self))
    #--- End: def

    def __str__(self):
        '''
x.__str__() <==> str(x)

''' 
        return '%s: %s' % (self.operator, repr(self.value))
    #--- End: def

    def copy(self):
        '''

Return a deep copy.

Equivalent to ``copy.deepcopy(c)``.

:Returns:

    out :
        The deep copy.

**Examples**

>>> c.copy()

'''
        return deepcopy(self)
    #--- End: def

    def dump(self, id=None):
        '''
        
Return a string containing a full description of the instance.

:Parameters:

    id : str, optional
       Set the common prefix of component names. By default the
       instance's class name is used.

:Returns:

    out : str
        A string containing the description.

**Examples**

>>> x = c.dump()
>>> print c.dump()
>>> print c.dump(id='comparison1')

'''
        return str(self)
    #--- End: def

    def evaluate(self, x):
        '''

Evaluate the comparison operation for a given object.

Note that ``x==c`` is equivalent to ``c.evaluate(x)`` and ``x!=c`` is
equivalent to ``c.evaluate(x)==False``.

:Parameters:

    x : object
        The object for the left hand side operand of the comparison
        operation.

:Returns:

    out : 
        The result of the operation given by the `operator` attribute
        with `x` as its left hand side operand and the `value`
        attribute as its right hand side operand.
    
**Examples**

>>> c = cf.Comparison('lt', 5.5)
>>> c.evaluate(6)
False

>>> c = cf.Comparison('wi', (1,2))
>>> array = numpy.arange(4)
>>> array
array([0, 1, 2, 3])
>>> c.evaluate(array)
array([False,  True,  True, False], dtype=bool)

'''
        operator = self.operator

        if operator == 'lt':
            return x < self.value

        if operator == 'le':
            return x <= self.value

        if operator == 'gt':
            return x > self.value

        if operator == 'ge':
            return x >= self.value

        if operator == 'eq':
            return x == self.value

        if operator == 'ne':
            return x != self.value

        if operator == 'wi':
            if self.value[0] < self.value[1]:
                out  = (x >= self.value[0])
                out &= (x <= self.value[1])
            else:
                out  = (x >= self.value[1])
                out &= (x <= self.value[0])
            #--- End: if
            return out
        #--- End: if           

        if operator == 'wo':
            if self.value[0] < self.value[1]:
                out  = (x < self.value[0])
                out |= (x > self.value[1])
            else:
                out  = (x < self.value[1])
                out |= (x < self.value[0])
            #--- End: if
            return out
        #--- End: if           

        if operator == 'set':
            if len(self.value) == 1:
                return x == self.value
            else:
                out = (x != x)
                for v in self.value:
                    out |= (x == v)

                return out
        #--- End: if           
    #--- End: def

#--- End: class

def lt(value, units=None):
    '''
    
Return an object for testing whether a variable is strictly less than
a given value.

:Parameters:

    value : object
        The value which a variable is to be compared with.

:Returns: 

    out : Comparison
        A Comparison object which will evaluate whether or not the
        comparison evaluates to True.

**Examples**

>>> c = cf.lt(5)
>>> c
<CF Comparison: x lt 5>
>>> c.evaluate(4)
True
>>> c.evaluate(5)
False

'''
    return Comparison('lt', value, units=units)
#--- End: def
    
def le(value, units=None):
    '''
    
Return an object for testing whether a variable is less than or equal
to the given value.

:Parameters:

    value : object
        The value which a variable is to be compared with.

:Returns: 

    out : Comparison
        A Comparison object which will evaluate whether or not the
        comparison evaluates to True.

**Examples**

>>> c = cf.le(5)
>>> c
<CF Comparison: x le 5>
>>> c.evaluate(5)
True
>>> c.evaluate(6)
False

'''
    return Comparison('le', value, units=units)
#--- End: def
    
def gt(value, units=None):
    '''
      
Return an object for testing whether a variable is greater than the
given value.

:Parameters:

    value : object
        The value which a variable is to be compared with.

:Returns: 

    out : Comparison
        A Comparison object which will evaluate whether or not the
        comparison evaluates to True.

**Examples**

>>> c = cf.gt(5)
>>> c
<CF Comparison: x gt 5>
>>> c.evaluate(6)
True
>>> c.evaluate(5)
False

'''
    return Comparison('gt', value, units=units)
#--- End: def
    
def ge(value, units=None):
    '''
     
Return an object for testing whether a variable is greater than or equal
to the given value.

:Parameters:

    value 
        The value which a variable is to be compared with.

:Returns:

    out : Comparison
        A Comparison object which will evaluate whether or not the comparison
        evaluates to True.

**Examples**

>>> c = cf.ge(5)
>>> c
<CF Comparison: x ge 5>
>>> c.evaluate(5)
True
>>> c.evaluate(4)
False

'''
    return Comparison('ge', value, units=units)
#--- End: def

# C-x f
    
def eq(value, units=None):
    '''
    
Return an object for testing whether a variable is equal to the given
value.

:Parameters:

    value 
        The value which a variable is to be compared with.

:Returns:

    out : Comparison
        A Comparison object which will evaluate whether or not the comparison
        evaluates to True.

**Examples**

>>> c = cf.eq(5)
>>> c
<CF Comparison: x eq 5>
>>> c.evaluate(5)
True
>>> c.evaluate(4)
False

'''
    return Comparison('eq', value, units=units)
#--- End: def
    
def ne(value, units=None):
    '''
    
Return an object for testing whether a variable is not equal to the
given value.

:Parameters:

    value : object
        The value which a variable is to be compared with.

:Returns: 

    out : Comparison
        A Comparison object which will evaluate whether or not the
        comparison evaluates to True.

**Examples**

>>> c = cf.ne(5)
>>> c
<CF Comparison: x ne 5>
>>> c.evaluate(4)
True
>>> c.evaluate(5)
False

'''
    return Comparison('ne', value, units=units)
#--- End: def
    
def wi(value0, value1, units=None):
    '''
    
Return an object for testing whether a variable is within the given
range.

:Parameters:

    value0 : object
         The lower bound of the range which a variable is to be
         compared with.

    value1 : object
         The upper bound of the range which a variable is to be
         compared with.

:Returns: 

    out : Comparison
        A Comparison object which will evaluate whether or not the
        comparison evaluates to True.

**Examples**

>>> c = cf.wi(5,7)
>>> c
<CF Comparison: x wi (5, 7)>
>>> c.evaluate(6)
True
>>> c.evaluate(4)
False

'''
    return Comparison('wi', [value0, value1], units=units)
#--- End: def

def wo(value0, value1, units=None):
    '''
    
Return an object for testing whether a variable is without the given
range.

:Parameters:

    value0 : object
         The lower bound of the range which a variable is to be
         compared with.

    value1 : object
         The upper bound of the range which a variable is to be
         compared with.

:Returns: 

    out : Comparison
        A Comparison object which will evaluate whether or not the
        comparison evaluates to True.

**Examples**

>>> c = cf.wo(5)
>>> c
<CF Comparison: x wo (5, 7)>
>>> c.evaluate(4)
True
>>> c.evaluate(6)
False

'''
    return Comparison('wo', [value0, value1], units=units)
#--- End: def

def set(values, units=None):
    '''
    
Return an object for testing whether a variable equals any element of
a collection.

:Parameters:

    values : scalar or tuple or list or set

:Returns: 

    out : Comparison
        A Comparison object which will evaluate whether or not the
        comparison evaluates to True.

**Examples**

>>> c = cf.set([3, 5])
>>> c
<CF Comparison: set [3, 5]>
>>> 4 == c
False
>>> 5 == c
True

>>> c = cf.set(4)
>>> c
<CF Comparison: set (4,)>
>>> 4 == c
True

'''
    return Comparison('set', values, units=units)
#--- End: def
